

(function(window) {
	
	/**
	 * @namespace Assert
	 */
	Types.exportSymbol("Assert", assertBuilder());
	Types.exportSymbol("AssertionError", AssertionError);

	function AssertionError(message) {
		this['name'] = 'AssertionError';
		this['message'] = message;
	};
	AssertionError.prototype = new Error;

	/**
	 * The assert tool is a configurable state checker for javascript. 
	 * 	It allows the developer to verify the state of a program while
	 *  it is under development, and later deactivate all such verifications
	 *  with a single switch.
	 * 
	 * For example:
	 * <pre>
	 *     Assert.assertsOn();
	 *     var aNullValue = null;
	 *  
	 *     Assert.assertNotNull(aNullValue, "This value should not be null.");
	 * </pre>
	 * Will fail with an AssertionError, but change Assert.assertsOn() to 
	 * 	Assert.assertsOff() (or simply omit the call to Assert.assertsOn() ) 
	 *  and the Assert.assertNotNull(...) method call will execute without 
	 *  error
	 * 
	 * @memberOf Assert
	 * @constructor 
	 */
	function assertBuilder() {

		var that = {};
		var assertsOn = false;

		/** 
		 * Returns true if asserts are turned on.
		 * @memberOf Assert
		 * @name areAssertsOn
		 * 
		 * @returns {boolean} true if asserts are on.
		 * @expose 
		 */
		that.areAssertsOn = function() {
			return assertsOn;
		};
		
		/** @expose */
		that.fail = function(msg) {
		};

		/** @expose */
		that.assert = function(test, msg) {
		};

		/**
		 * @memberOf Assert
		 * @name assertEvalsTrue
		 * 
		 * @property {Function} a zero argument function to evaluate. This assert will fail if it does not return true.
		 * @property {String} msg [Optional] the message to return in the error if the specified value is null. 
		 * @expose
		 */
		that.assertEvalsTrue = function(funcToEval, msg) {
		};
		
		/** 
		 * Returns true if asserts are turned on.
		 * 
		 * @memberOf Assert
		 * @name assertNotNull
		 * @static
		 * 
		 * @property {mixed} a the object to check 
		 * @property {String} msg [Optional] the message to return in the error if the specified value is null. 
		 * 
		 * @expose 
		 */
		that.assertNotNull = function(a, msg) {
		};

		/** @expose */
		that.assertNotEquals = function(a, b, msg) {
		};

		/** @expose */
		that.assertEquals = function(a, b, msg) {
		};

		/** @expose */
		that.assertsOff = function() {
			Logger.debug("Turning asserts off");
			assertsOn = false;
			that.assertDefined = function(a, msg) {
			};
			that.assertNotEquals = function(a, b, msg) {
			};
			that.assertEquals = function(a, b, msg) {
			};
			that.assert = function(test, msg) {
			};
			that.assertEvalsTrue = function(funcToEval, msg) {
			};
		};

		/** 
		 * Turn asserts on. Any failing asserts after this method call 
		 * 	will result in an exception.
		 * 
		 * @memberOf Assert
		 * @name assertsOn
		 * 
		 * @expose 
		 */
		that.assertsOn = function() {
			Logger.debug("Turning asserts on");
			assertsOn = true;

			that.fail = function(msg) {
				that._assert(false, msg);
			};
			
			that.assertEvalsTrue = function(funcToEval, msg) {
				that._assert(funcToEval(), msg);
			};

			that.assertNotNull = function(a, msg) {
				msg = msg || "expected argument to be not null.";
				if (a == null) {
					that._assert(false, msg);
				}
			};

			that.assertNotEquals = function(a, b, msg) {
				msg = msg || "expected not" + a + " but got " + b + " instead.";
				that._assert(!deepEquals(a, b), msg);
			};

			that.assertEquals = function(a, b, msg) {
				msg = msg || "expected " + a + " but got " + b + " instead.";
				that._assert(deepEquals(a, b), msg);
			};

			that.assert = function(test, msg) {
				//we do this so that we always have the same number of stack frames
				that._assert(test, msg);
			}

			that._assert = function(test, msg) {
				msg = msg || "(no error message)";
				if (!test) {
					throw new AssertionError(": " + msg + " -- assertion failed at " + Utilities.getStackFrame(1));
				}
			};
		};
		return that;
	};

	function deepEquals(objectA, objectB) {
		if(objectA == objectB) {
			return true;
		}
		if(typeof objectA != 'object') {
			return false;
		}
		if(typeof objectB != 'object') {
			return false;
		}
		var p;
		for (p in objectA) {
			if ( typeof (objectB[p]) == 'undefined') {
				return false;
			}
		}
		for (p in objectA) {
			if (objectA[p]) {
				switch(typeof(objectA[p])) {
					case 'object':
						if (!objectA[p].equals(objectB[p])) {
							return false;
						}
						break;
					case 'function':
						if ( typeof (objectB[p]) == 'undefined' || (p != 'equals' && objectA[p].toString() != objectB[p].toString()))
							return false;
						break;
					default:
						if (objectA[p] != objectB[p]) {
							return false;
						}
				}
			} else {
				if (objectB[p])
					return false;
			}
		}

		for (p in objectB) {
			if ( typeof (objectA[p]) == 'undefined') {
				return false;
			}
		}
		return true;
	};

})(this);
